# -*- coding: utf-8 -*-
# Author   : ZhangQing
# Time     : 2025-08-05 23:27
# File     : file.py
# Project  : codebuddy_craft
# Desc     :
# backend/schemas/file.py
"""
文件数据模式定义 📄

定义文件相关的请求和响应数据结构
"""

from pydantic import BaseModel, Field, validator
from typing import Optional, Dict, Any, List
from datetime import datetime


class FileBase(BaseModel):
    """文件基础模型"""
    name: str = Field(..., min_length=1, max_length=255, description="文件名")
    path: str = Field(..., min_length=1, max_length=500, description="文件路径")
    file_type: Optional[str] = Field(None, max_length=50, description="文件类型")
    encoding: str = Field(default="utf-8", description="文件编码")
    is_hidden: bool = Field(default=False, description="是否为隐藏文件")
    metadata: Optional[Dict[str, Any]] = Field(default_factory=dict, description="文件元数据")


class FileCreateRequest(FileBase):
    """创建文件请求模型 ✨"""
    content: Optional[str] = Field(default="", description="文件内容")

    @validator('name')
    def validate_filename(cls, v):
        """验证文件名"""
        if not v.strip():
            raise ValueError("文件名不能为空")

        # 禁止的字符
        forbidden_chars = ['<', '>', ':', '"', '|', '?', '*', '\\', '/']
        for char in forbidden_chars:
            if char in v:
                raise ValueError(f"文件名不能包含字符: {char}")

        return v.strip()

    @validator('path')
    def validate_path(cls, v):
        """验证文件路径"""
        if not v.strip():
            raise ValueError("文件路径不能为空")

        # 禁止路径遍历
        if '..' in v or v.startswith('/'):
            raise ValueError("文件路径不能包含 '..' 或以 '/' 开头")

        return v.strip()

    @validator('content')
    def validate_content(cls, v):
        """验证文件内容"""
        if v is None:
            return ""

        # 检查内容大小 (5MB 限制)
        max_size = 5 * 1024 * 1024
        if len(v.encode('utf-8')) > max_size:
            raise ValueError(f"文件内容不能超过 {max_size // (1024 * 1024)}MB")

        return v


class FileUpdateRequest(BaseModel):
    """更新文件请求模型 ✏️"""
    name: Optional[str] = Field(None, min_length=1, max_length=255, description="文件名")
    path: Optional[str] = Field(None, min_length=1, max_length=500, description="文件路径")
    content: Optional[str] = Field(None, description="文件内容")
    is_hidden: Optional[bool] = Field(None, description="是否为隐藏文件")
    metadata: Optional[Dict[str, Any]] = Field(None, description="文件元数据")

    @validator('name')
    def validate_filename(cls, v):
        """验证文件名"""
        if v is None:
            return v

        if not v.strip():
            raise ValueError("文件名不能为空")

        # 禁止的字符
        forbidden_chars = ['<', '>', ':', '"', '|', '?', '*', '\\', '/']
        for char in forbidden_chars:
            if char in v:
                raise ValueError(f"文件名不能包含字符: {char}")

        return v.strip()

    @validator('path')
    def validate_path(cls, v):
        """验证文件路径"""
        if v is None:
            return v

        if not v.strip():
            raise ValueError("文件路径不能为空")

        # 禁止路径遍历
        if '..' in v or v.startswith('/'):
            raise ValueError("文件路径不能包含 '..' 或以 '/' 开头")

        return v.strip()

    @validator('content')
    def validate_content(cls, v):
        """验证文件内容"""
        if v is None:
            return v

        # 检查内容大小 (5MB 限制)
        max_size = 5 * 1024 * 1024
        if len(v.encode('utf-8')) > max_size:
            raise ValueError(f"文件内容不能超过 {max_size // (1024 * 1024)}MB")

        return v


class FileResponse(BaseModel):
    """文件响应模型 📖"""
    id: str = Field(..., description="文件ID")
    name: str = Field(..., description="文件名")
    path: str = Field(..., description="文件路径")
    content: Optional[str] = Field(None, description="文件内容")
    file_type: Optional[str] = Field(None, description="文件类型")
    encoding: str = Field(..., description="文件编码")
    size: int = Field(..., description="文件大小(字节)")
    lines: int = Field(..., description="行数")
    is_binary: bool = Field(..., description="是否为二进制文件")
    is_hidden: bool = Field(..., description="是否为隐藏文件")
    version: int = Field(..., description="文件版本")
    checksum: Optional[str] = Field(None, description="文件校验和")
    metadata: Dict[str, Any] = Field(default_factory=dict, description="文件元数据")
    project_id: str = Field(..., description="所属项目ID")
    created_at: Optional[datetime] = Field(None, description="创建时间")
    updated_at: Optional[datetime] = Field(None, description="更新时间")
    last_accessed_at: Optional[datetime] = Field(None, description="最后访问时间")

    class Config:
        from_attributes = True
        json_encoders = {
            datetime: lambda v: v.isoformat() if v else None
        }

    @classmethod
    def from_orm(cls, obj, include_content: bool = True):
        """从ORM对象创建响应模型"""
        data = {
            'id': obj.id,
            'name': obj.name,
            'path': obj.path,
            'file_type': obj.file_type,
            'encoding': obj.encoding,
            'size': obj.size,
            'lines': obj.lines,
            'is_binary': obj.is_binary,
            'is_hidden': obj.is_hidden,
            'version': obj.version,
            'checksum': obj.checksum,
            'metadata': obj.metadata or {},
            'project_id': obj.project_id,
            'created_at': obj.created_at,
            'updated_at': obj.updated_at,
            'last_accessed_at': obj.last_accessed_at
        }

        # 根据需要包含文件内容
        if include_content:
            data['content'] = obj.content

        return cls(**data)

    def get_file_extension(self) -> str:
        """获取文件扩展名"""
        return self.name.split('.')[-1].lower() if '.' in self.name else ''

    def is_code_file(self) -> bool:
        """判断是否为代码文件"""
        code_extensions = {
            'py', 'js', 'ts', 'jsx', 'tsx', 'html', 'css', 'scss', 'less',
            'rs', 'go', 'java', 'cpp', 'c', 'h', 'hpp', 'cs', 'php',
            'rb', 'swift', 'kt', 'scala', 'clj', 'elm', 'hs', 'ml',
            'json', 'yaml', 'yml', 'toml', 'ini', 'cfg', 'xml'
        }
        return self.get_file_extension() in code_extensions

    def get_readable_size(self) -> str:
        """获取可读的文件大小"""
        size = self.size
        for unit in ['B', 'KB', 'MB', 'GB']:
            if size < 1024.0:
                return f"{size:.1f} {unit}"
            size /= 1024.0
        return f"{size:.1f} TB"


class FileListResponse(BaseModel):
    """文件列表响应模型 📋"""
    items: List[FileResponse] = Field(..., description="文件列表")
    total: int = Field(..., description="文件总数")

    class Config:
        from_attributes = True


class FileUploadResponse(BaseModel):
    """文件上传响应模型 📤"""
    file_id: str = Field(..., description="文件ID")
    message: str = Field(..., description="响应消息")
    file_info: FileResponse = Field(..., description="文件信息")

    class Config:
        from_attributes = True


class FileBatchOperation(BaseModel):
    """批量文件操作模型 📦"""
    file_ids: List[str] = Field(..., min_items=1, description="文件ID列表")
    operation: str = Field(..., description="操作类型: delete, move, copy")
    target_path: Optional[str] = Field(None, description="目标路径(用于move和copy操作)")

    @validator('operation')
    def validate_operation(cls, v):
        """验证操作类型"""
        allowed_operations = ['delete', 'move', 'copy']
        if v not in allowed_operations:
            raise ValueError(f"操作类型必须是: {', '.join(allowed_operations)}")
        return v

    @validator('target_path')
    def validate_target_path(cls, v, values):
        """验证目标路径"""
        operation = values.get('operation')
        if operation in ['move', 'copy'] and not v:
            raise ValueError(f"{operation} 操作需要指定目标路径")

        if v and ('..' in v or v.startswith('/')):
            raise ValueError("目标路径不能包含 '..' 或以 '/' 开头")

        return v


class FileSearchRequest(BaseModel):
    """文件搜索请求模型 🔍"""
    query: str = Field(..., min_length=1, description="搜索关键词")
    file_types: Optional[List[str]] = Field(None, description="文件类型过滤")
    path_filter: Optional[str] = Field(None, description="路径过滤")
    content_search: bool = Field(default=False, description="是否搜索文件内容")
    case_sensitive: bool = Field(default=False, description="是否区分大小写")

    @validator('query')
    def validate_query(cls, v):
        """验证搜索关键词"""
        if not v.strip():
            raise ValueError("搜索关键词不能为空")
        return v.strip()


class FileSearchResponse(BaseModel):
    """文件搜索响应模型 🔍"""
    files: List[FileResponse] = Field(..., description="匹配的文件列表")
    matches: List[Dict[str, Any]] = Field(..., description="匹配详情")
    total: int = Field(..., description="匹配总数")
    query: str = Field(..., description="搜索关键词")

    class Config:
        from_attributes = True


class FileTreeNode(BaseModel):
    """文件树节点模型 🌳"""
    name: str = Field(..., description="文件/文件夹名")
    path: str = Field(..., description="完整路径")
    type: str = Field(..., description="类型: file 或 folder")
    file_id: Optional[str] = Field(None, description="文件ID(仅文件类型)")
    size: Optional[int] = Field(None, description="文件大小(仅文件类型)")
    children: Optional[List['FileTreeNode']] = Field(None, description="子节点(仅文件夹类型)")

    class Config:
        from_attributes = True


class FileTreeResponse(BaseModel):
    """文件树响应模型 🌳"""
    tree: List[FileTreeNode] = Field(..., description="文件树结构")
    total_files: int = Field(..., description="文件总数")
    total_folders: int = Field(..., description="文件夹总数")

    class Config:
        from_attributes = True


class FileCompareRequest(BaseModel):
    """文件比较请求模型 🔄"""
    file1_id: str = Field(..., description="第一个文件ID")
    file2_id: str = Field(..., description="第二个文件ID")
    ignore_whitespace: bool = Field(default=False, description="是否忽略空白字符")
    context_lines: int = Field(default=3, ge=0, le=10, description="上下文行数")


class FileDiffLine(BaseModel):
    """文件差异行模型"""
    line_number: Optional[int] = Field(None, description="行号")
    content: str = Field(..., description="行内容")
    change_type: str = Field(..., description="变更类型: add, remove, context")


class FileCompareResponse(BaseModel):
    """文件比较响应模型 🔄"""
    file1_info: FileResponse = Field(..., description="第一个文件信息")
    file2_info: FileResponse = Field(..., description="第二个文件信息")
    diff_lines: List[FileDiffLine] = Field(..., description="差异行列表")
    added_lines: int = Field(..., description="新增行数")
    removed_lines: int = Field(..., description="删除行数")
    similarity: float = Field(..., description="相似度(0-1)")

    class Config:
        from_attributes = True


# 更新 FileTreeNode 以支持前向引用
FileTreeNode.model_rebuild()
